parser grammar Javasint;

/*
//Solucion del if con backtrack=false:
if_statement: IF^ ifexp ifbloque (elseif)?;
ifexp: LPARENTHESIS! expression RPARENTHESIS!; 
ifbloque: (bloque_stats | statement); 
elseif: ELSE^ (bloque_stats | statement);

*/


options {
  language = Java;
  tokenVocab = Javalex;
  backtrack = true; //para resolver ambiguedades
  //k = 3;
}

/*@header {
	import java.io.BufferedWriter;
	import java.io.FileWriter;
}*/

@members {
	SymTable tabla = new SymTable();
	boolean tipoFunc = false; //para saber si funcion devuelve tipo o no
	StringBuilder codigoSAM = new StringBuilder(); // Contiene el codigo SAM que se va generando dinamicamente
	boolean debug = true;
	boolean ref = false;
	
	void printTS(String mensaje) {
		if (debug)
		  System.out.println(mensaje + tabla);
	}
	
  protected void mismatch(IntStream input, int ttype, BitSet follow)
    throws RecognitionException
  {
    throw new MismatchedTokenException(ttype, input);
  } 
}


@rulecatch { 
  catch (RecognitionException e) {
    throw e;
  } 
}

//program: data_def* function_def* EOF ;

program: def def* EOF { codigoSAM.append("\nHALT"); printTS("\nFINAL: \n"); } ;
def: (INT? ID LPARENTHESIS) => function_def | data_def;

data_def:
        INT d1=declarator { tabla.lookUp($d1.name, EntryTS.VAR, 0, Tokens.INT); codigoSAM.append("\nPUSH 1"); }  
        (COMMA d2=declarator { tabla.lookUp($d2.name, EntryTS.VAR, 0, Tokens.INT); codigoSAM.append("\nPUSH 1"); })* SEMICOLON
        
        | LONG d1=declarator { tabla.lookUp($d1.name, EntryTS.VAR, 0, Tokens.LONG); codigoSAM.append("\nPUSH 2"); }  
        (COMMA d2=declarator { tabla.lookUp($d2.name, EntryTS.VAR, 0, Tokens.LONG); codigoSAM.append("\nPUSH 2"); })* SEMICOLON;
         
declarator returns [String name]: 
    id=ID {$declarator.name = $id.getText();};
    
function_def: 
        { printTS("\nANTES DE FUNCION:\n"); } 
        (INT {tipoFunc = true;} )? 
        function_header  //funcion_header introduce funcion, blkPush(), parametros     
        function_body 
        { printTS("\nAL FINAL DE LA FUNCION: \n");
          tabla.blkPop(); 
          printTS("\nDESPUES DE POP: \n");
          if (tipoFunc)
             codigoSAM.append("\nRETURN (1) 1"); //funcion retorna algo
          else 
             codigoSAM.append("\nRETURN (0) 1"); 
            
          tipoFunc = false; //para las siguientes funciones 
        } ;
        
function_header: //returns [String name] : 
            d=declarator 
           { codigoSAM.append("\n\n" + $d.name + ":");
            if ($d.name.equals("main"))
               tabla.modify("main", EntryTS.FUNC, 0, Tokens.INT);
            else {
                if (tipoFunc)
                    tabla.lookUp($d.name, EntryTS.FUNC, 0, Tokens.INT); 
                else
                    tabla.lookUp($d.name, EntryTS.FUNC, 0, 0); 
            }
            
             tabla.blkPush(); //push despues de nombre de funcion y antes de parametros
            }
            
            parameter_list;
            
            
parameter_list: LPARENTHESIS identifier_list? RPARENTHESIS parameter_decl*;

identifier_list: (AND_BIN { ref = true;} )? id1=ID 
         { 
           if (ref) {
               codigoSAM.append("\nLOADA "); //paso por referencia
               tabla.lookUp($id1.getText(), EntryTS.PARAM_REF, 0, Tokens.INT); //tipo int por defecto 
           }
           else {
               codigoSAM.append("\nLOAD (1) "); //paso por valor
               tabla.lookUp($id1.getText(), EntryTS.PARAM, 0, Tokens.INT); //tipo int por defecto 
           }
           
           codigoSAM.append(tabla.findOffset($id1.getText()) + "[LB]");
           ref = false; 
          } 
         
         (COMMA (AND_BIN { ref = true;} )? id2=ID 
         { tabla.lookUp($id2.getText(), EntryTS.PARAM, 0, Tokens.INT);
           if (ref) {
               codigoSAM.append("\nLOADA "); //paso por referencia
               tabla.lookUp($id1.getText(), EntryTS.PARAM_REF, 0, Tokens.INT); //tipo int por defecto 
           }
           else {
               codigoSAM.append("\nLOAD (1) "); //paso por valor
               tabla.lookUp($id1.getText(), EntryTS.PARAM, 0, Tokens.INT); //tipo int por defecto 
           }
           codigoSAM.append(tabla.findOffset($id1.getText()) + "[LB]");
           ref = false;  
          
         } )*;

parameter_decl: INT d1=declarator 
               { tabla.lookUp($d1.name, EntryTS.PARAM, 0, Tokens.INT); 
                  codigoSAM.append("\nLOAD (1) " + tabla.findOffset($d1.name) + "[LB]"); }
                  
               (COMMA d2=declarator 
               { tabla.lookUp($d2.name, EntryTS.PARAM, 0, Tokens.INT); 
               codigoSAM.append("\nLOAD (1) " + tabla.findOffset($d2.name) + "[LB]"); })* SEMICOLON;
               
               
function_body: LCURLY data_def* statement* RCURLY;
statement: expression? SEMICOLON
        | LCURLY { tabla.blkPush();} data_def* statement* { tabla.blkPop();} RCURLY
        | IF LPARENTHESIS expression RPARENTHESIS statement (ELSE statement)?
        | WHILE LPARENTHESIS expression RPARENTHESIS statement
        | BREAK SEMICOLON
        | CONTINUE SEMICOLON
        | RETURN (expression)? SEMICOLON 
         { if (tabla.getBlockLevel() == 0)
         	codigoSAM.append("\nHALT");
           else
            codigoSAM.append("\nRETURN (0) 0"); //aun por saber
         };

expression: binary (COMMA expression)*;
binary returns [long value]: 
       id=ID ASSIGN b=binary 
       { EntryTS entrada = tabla.modifyValue($id.getText(), $b.value);
         if (entrada != null) //si es null: error semantico
            if (entrada.getKind() == EntryTS.PARAM_REF)
               codigoSAM.append("\nSTOREI (1)");
            else 
               codigoSAM.append("\nSTORE (1) 1[LB]"); 
       }
       
        
       | ID APLUS binary
       | ID AMULT binary
       | ID AMINUS binary
       | ID ADIV binary
       | ID AMOD binary
       
       | unary PLUS binary { codigoSAM.append("\nCALL (1) add"); }
       | unary MINUS binary { codigoSAM.append("\nCALL (1) sub"); }
       | unary ASTERISK binary { codigoSAM.append("\nCALL (1) mult"); }
       | unary DIV binary { codigoSAM.append("\nCALL (1) div"); }
       | unary MOD binary
       | unary AND_BIN binary
       | unary OR_BIN binary
       | unary POW binary
       
       | unary LESS binary
       | unary GREATER binary
       | unary NOTEQUAL binary
       | unary EQUALS binary
       | unary LESSEQUAL binary
       | unary GREATEQUAL binary
       
       | unary { $binary.value = $unary.value;} ;
       
unary returns [long value] : 
            INC ID 
            | ID INC
            | DEC ID
            | ID DEC
            | primary { $unary.value = $primary.value; } ;
            
primary returns [long value] : 
       id=ID { $primary.value = tabla.findValue($id.getText()); } // cuidado si no existe
        | n=NUMERO { $primary.value = Long.parseLong($n.getText()); codigoSAM.append("\nLOADL 1"); } // NUMERO = CONST 
        | LPARENTHESIS expression RPARENTHESIS
        | id=ID LPARENTHESIS argument_list? RPARENTHESIS 
          {codigoSAM.append("\nCALL (SB) " + $id.getText()); };  //function_statement
        
argument_list: binary (COMMA binary)*; 
       

